package com.lianqi.emcpframework.extend.script.engine;

import com.lianqi.emcpframework.extend.script.ExecuteResult;
import com.lianqi.emcpframework.extend.script.ListenerSupportEngine;
import com.lianqi.emcpframework.extend.script.ScriptContext;
import com.lianqi.emcpframework.extend.script.ScriptListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.DigestUtils;

import javax.script.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @program emcp-framework
 * @ClassName CommonScriptEngine
 * @description: 通用脚本引擎
 * @author: sky
 * @create: 2018/12/05 11:21
 */
@Slf4j
public abstract class CommonScriptEngine extends ListenerSupportEngine{
    /**
     * 编译器
     */
    protected Compilable compilable;

    protected Map<String,CommonScriptContext> scriptCache = new ConcurrentHashMap();

    protected Bindings bindings;

    protected List<ScriptListener> listeners;

    public abstract String getScriptName();

    @Override
    public void init(String... contents) throws Exception {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName(getScriptName());
        compilable = (Compilable) engine;
        bindings = engine.createBindings();
        CompiledScript script;
        for (String content : contents){
            script = compilable.compile(content);
            script.eval(bindings);
        }
    }

    @Override
    public boolean compile(String id, String script) throws Exception {
        if (log.isDebugEnabled()){
            log.debug("compile {} {} : {}",getScriptName(),id,script);
        }
        if (compilable == null){
            init();
        }
        CompiledScript compiledScript = compilable.compile(script);
        CommonScriptContext scriptContext = new CommonScriptContext(id,
                DigestUtils.md5DigestAsHex(script.getBytes()),compiledScript);
        scriptCache.put(id,scriptContext);
        return true;
    }

    @Override
    public ScriptContext getContext(String id) {
        return scriptCache.get(id);
    }

    @Override
    public boolean complied(String id) {
        return scriptCache.containsKey(id);
    }

    @Override
    public boolean remove(String id) {
        return scriptCache.remove(id) != null;
    }

    @Override
    public void addGlobalVariable(Map<String, Object> vars) {
        bindings.putAll(vars);
    }

    public Bindings getBindings() {
        return bindings;
    }

    public void setBindings(Bindings bindings) {
        this.bindings = bindings;
    }

    @Override
    public ExecuteResult excute(String id, Map<String, Object> params) {
        if (log.isDebugEnabled()){
            log.debug("excute script {} {} : {}",getScriptName(),id,params);
        }
        long start = System.currentTimeMillis();
        ExecuteResult result = new ExecuteResult();
        CommonScriptContext context = scriptCache.get(id);
        try {
            if (context != null){
                doListenerBefore(context);
                javax.script.ScriptContext scriptContext = new SimpleScriptContext();
                scriptContext.setBindings(bindings, javax.script.ScriptContext.GLOBAL_SCOPE);
                for (Map.Entry<String,Object> entry : params.entrySet()){
                    scriptContext.setAttribute(entry.getKey(),entry.getValue(), javax.script.ScriptContext.ENGINE_SCOPE);
                }
                result.setSuccess(true);
                result.setResult(context.getScript().eval(scriptContext));
            }else {
                result.setSuccess(false);
                result.setResult(null);
                result.setMessage(String.format("script(%s): %s not found!",getScriptName(),id));
            }
            long end = System.currentTimeMillis();
            result.setUseTime(end - start);
        }catch (ScriptException e){
                result.setException(e);
                result.setMessage(e.getMessage());
        }
        doListenerAfter(context,result);
        return result;
    }

    @Override
    public ExecuteResult excute(String id) {
        return excute(id,new HashMap<>());
    }

    class CommonScriptContext extends ScriptContext{
        private CompiledScript script;
        public CommonScriptContext(String id, String md5,CompiledScript script) {
            super(id, md5);
            this.script = script;
        }

        public CompiledScript getScript(){
            return script;
        }
    }
}
